﻿
//	------------------------------------------------------------------------------
//                                       _                          
//              _ __ ___   ___ _ __ ___ | |__  _ __ __ _ _ __   ___ 
//             | '_ ` _ \ / _ \ '_ ` _ \| '_ \| '__/ _` | '_ \ / _ \
//             | | | | | |  __/ | | | | | |_) | | | (_| | | | |  __/
//             |_| |_| |_|\___|_| |_| |_|_.__/|_|  \__,_|_| |_|\___|
//                                                     
//	------------------------------------------------------------------------------

#region - License -
//
//	------------------------------------------------------------------------------                                                   
//	         Copyright (c) 2010 Peter Vyvey (peter.vyvey@chiliware.be)
//	------------------------------------------------------------------------------
//
//
//	This file is part of CHILIWARE MEMBRANE.
//
//	CHILIWARE MEMBRANE is free software: you can redistribute it and/or modify
//	it under the terms of the GNU Lesser General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	CHILIWARE MEMBRANE is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU Lesser General Public License for more details.
//
//	You should have received a copy of the GNU Lesser General Public License
//	along with CHILIWARE MEMBRANE.  If not, see <http://www.gnu.org/licenses/>.
//
//
#endregion

#region - Changes -
//
//	peter.vyvey@chiliware.be		2010-08-10		Created
//
#endregion

#region - Using -

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Chiliware.Membrane.Configuration;
using Chiliware.Membrane.ExceptionManagement;
using Chiliware.Membrane.Pattern.Creational;
using Chiliware.Membrane.Security.Authentication;
using Chiliware.Membrane.Security.Authorization;

#endregion

namespace Chiliware.Membrane.Model
{
	#region - ApplicationModel -

	/// <summary>
    /// Holds the references to the application's sessions, services and object repositories.
    /// </summary>
    /// <remarks>
    /// This APPLICATIONMODEL object is typicaly used as a singleton in the application.
    /// </remarks>
    public sealed partial class ApplicationModel 
		: IApplicationModel, IDisposable
    {
        #region - Constructors -

        /// <summary>
        /// Default constructor.
        /// </summary>
        public ApplicationModel() 
        {
            this.InitializeObject();
        }

        #endregion

        #region - Destructor -

        /// <summary>
        /// Destructor.
        /// </summary>
        ~ApplicationModel()
        {
            this.Dispose(false);
        }

        #endregion

		#region - Constants & static fields -

		/// <summary>
		/// The default options for parallel tasks.
		/// </summary>
		public static readonly ParallelOptions DEFAULT_PARALLEL_OPTIONS = new ParallelOptions { MaxDegreeOfParallelism = 2 };

		#endregion

		#region - Delegates -

		/// <summary>
        /// Delegate defining the signature for the method of the configured preloaders.
        /// </summary>
        private delegate void Preload();

        #endregion

        #region - Private fields -

        /// <summary>
        /// Container for the thread local SessionID.
        /// </summary>
        private ThreadLocal<Guid> threadSessionID = new ThreadLocal<Guid>(() => Guid.Empty);

		/// <summary>
		/// TRUE if the object is disposed, FALSE otherwise.
		/// </summary>
		private bool isDisposed = false;

        /// <summary>
        /// A dictionary with the registered portal types.
        /// </summary>
		private readonly ConcurrentDictionary<object, Portal> portals = new ConcurrentDictionary<object, Portal>();

        /// <summary>
        /// A authentication service.
        /// </summary>
		private IAuthenticationService authenticationService;

		/// <summary>
		/// The authorization service.
		/// </summary>
		private IAuthorizationService authorizationService;

		/// <summary>
		/// The sequencer service.
		/// </summary>
		private ISequencerService sequencerService;

		/// <summary>
		/// The audit service.
		/// </summary>
		private IAuditService auditService;

		/// <summary>
		/// The user profile service.
		/// </summary>
		private IUserProfileService userProfileService;

        /// <summary>
        /// The session service manages the workspace sessions.
        /// </summary>
        private readonly SessionService sessionService = Singleton<SessionService>.Instance;

        #endregion

        #region - Properties -

		/// <summary>
		/// Returns the singleton instance of an <see cref="IApplicationModel"/> implementation.
		/// </summary>
		public static IApplicationModel Instance { get { return Singleton<ApplicationModel>.Instance; } }

        /// <summary>
        /// The current session identifier. Points to the thread specific container for the identifier.
        /// </summary>
        /// <remarks>Still we have to try to implement some thread-safety checking while accessing the SessionID.</remarks>
        private Guid SessionID
        {
            get
            {
                SessionService.Session session;
                if (this.sessionService.TryGetSession(this.threadSessionID.Value, out session))
                {
                    return this.threadSessionID.Value;
                }
                else
                {
					throw new SessionException(ExceptionNumber.SessionExpired);
                }
            }
            set
            {
                SessionService.Session session;
                if (this.sessionService.TryGetSession(value, out session))
                {
                    this.threadSessionID.Value = value;
                }
                else
                {
                    throw new SessionException(ExceptionNumber.SessionExpired);
                }
            }
        }

		/// <summary>
		/// Returns TRUE if a valid <see cref="SessionService.Session"/> is attached the current WORKSPACEHOST, FALSE otherwise.
		/// </summary>
		private bool HasValidSession
		{
			get
			{
				SessionService.Session session;
				return this.sessionService.TryGetSession(this.threadSessionID.Value, out session);
			}
		}

        #endregion

        #region - Public methods -

        /// <summary>
        /// Disposes the object.
        /// </summary>
        public void Dispose()
        {
			if (!this.isDisposed)
			{
				this.Dispose(true);
			}

            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Retrieves the portal with the [requested|technology specific] name.
        /// </summary>
		/// <param name="portalName">Name of the [requested|technology specific] portal.</param>
        /// <returns>The portal connector.</returns>
		public Portal GetPortal(string portalName)
		{
			Portal portal;
			if (!this.portals.TryGetValue(portalName, out portal))
			{
				this.portals[portalName] = portal = new Portal();
			}

			return portal;
		}

		/// <summary>
		/// Gets the parameters for the current session.
		/// </summary>
		/// <returns>The session parameters.</returns>
		public Dictionary<string, string> GetSessionParameters()
		{
			Dictionary<string, string> parameters = default(Dictionary<string, string>);

			SessionService.Session session = null;
			if (this.sessionService.TryGetSession(this.SessionID, out session))
			{
				parameters = session.GetSessionParameters();
			}

			return parameters;
		}

		/// <summary>
		/// Get the user ID for the current session.
		/// </summary>
		/// <returns>The user ID.</returns>
		public string GetUserID()
		{
			string userID = string.Empty;

			SessionService.Session session = null;
			if (this.sessionService.TryGetSession(this.SessionID, out session))
			{
				userID = session.UserID;
			}

			return userID;
		}

		/// <summary>
		/// Authorizes the access to the resource for a user.
		/// </summary>
		/// <param name="resourcePath">The resource path.</param>
		/// <returns>The permission.</returns>
		public Permission GetPermission(string resourcePath)
		{
			Permission permission = Permission.NoAccess;

			SessionService.Session session = null;
			if (this.sessionService.TryGetSession(this.SessionID, out session))
			{
				permission = this.authorizationService.GetPermission(session.UserID, resourcePath);
			}
			
			return permission;
		}

		/// <summary>
		/// Increments the value of the sequencer with the given code.
		/// </summary>
		/// <param name="code">The sequencer code.</param>
		public void IncrementSequencerNextValue(string code)
		{
			this.sequencerService.IncrementValue(code);
		}

		/// <summary>
		/// Increments and returns the value of the sequencer with the given code.
		/// </summary>
		/// <param name="code">The sequencer code.</param>
		/// <returns>The sequencer value.</returns>
		public int GetSequencerNextValue(string code)
		{
			return this.sequencerService.GetNextValue(code);
		}

		/// <summary>
		/// Audits a message to the workspace backend store.
		/// </summary>
		/// <param name="entryType">The type of audit entry.</param>
		/// <param name="message">The audit message.</param>
		public void Audit(AuditEntryType entryType, string message)
		{
			SessionService.Session session = null;
			if (this.sessionService.TryGetSession(this.SessionID, out session))
			{
				this.auditService.Audit(session.SessionRegistryID, entryType, message);
			}
		}

		/// <summary>
		/// Audits a message to the workspace back end store.
		/// </summary>
		/// <param name="entryType">The type of audit entry.</param>
		/// <param name="message">The audit message.</param>
		/// <param name="data">The additional audit object.</param>
		public void Audit<T>(AuditEntryType entryType, string message, T data)
		{
			SessionService.Session session = null;
			if (this.sessionService.TryGetSession(this.SessionID, out session))
			{
				this.auditService.Audit<T>(session.SessionRegistryID, entryType, message, data);
			}
		}

        #endregion

        #region - Private & protected methods -

        /// <summary>
        /// Initializes the ApplicationService.
        /// </summary>
        private void InitializeObject()
        {
			this.ConfigureServices();
            this.ExecutePreloaders();
        }

		/// <summary>
		/// Configures the workspace services.
		/// </summary>
		private void ConfigureServices()
		{
			if (null != WorkspaceConfiguration.Configuration)
			{
				Configuration.Environment environment = WorkspaceConfiguration.Configuration.Host.Environments.GetEnabledEnvironment();

				// Configure the AUTHENTICATIONSERVICE.
				Type authenticationServiceType = Type.GetType(environment.AuthenticationService.Type);
				this.authenticationService = Activator.CreateInstance(authenticationServiceType) as IAuthenticationService;

				// Configure the AUTHORIZATIOnSERVICE.
				Type authorizatinServiceType = Type.GetType(environment.AuthorizationService.Type);
				this.authorizationService = Activator.CreateInstance(authorizatinServiceType) as IAuthorizationService;

				// Configure the SEQUENCERSERVICE.
				Type sequencerServiceType = Type.GetType(environment.SequencerService.Type);
				this.sequencerService = Activator.CreateInstance(sequencerServiceType) as ISequencerService;

				// Configure the AUDITSERVICE.
				Type auditServiceType = Type.GetType(environment.AuditService.Type);
				this.auditService = Activator.CreateInstance(auditServiceType) as IAuditService;

				// Configure the SEQUENCERSERVICE.
				Type userProfileServiceType = Type.GetType(environment.UserProfileService.Type);
				this.userProfileService = Activator.CreateInstance(userProfileServiceType) as IUserProfileService;
			}
		}

        /// <summary>
        /// Preload and initialize objects that take a longer time to load. This way we create a more responsive application.
        /// </summary>
        private void ExecutePreloaders()
        {
            Preload preload =
                delegate()
                {
					try
					{
						if (null != WorkspaceConfiguration.Configuration)
						{
							var preloaders = WorkspaceConfiguration.Configuration.Host.Environments[0].Preloaders;

							foreach (Preloader preloader in preloaders)
							{
								try
								{
									// Grab the type that has the static method
									Type type = Type.GetType(preloader.Type);

									// Grab the specific static method
									MethodInfo methodInfo = type.GetMethod(preloader.Method, BindingFlags.Static | BindingFlags.Public);

									// Execute the method
									methodInfo.Invoke(null, null);
								}
								finally { /* IGNORE */}
							}
						}
					}
					finally { }
                };

            IAsyncResult result = preload.BeginInvoke(null, null);
        }

        /// <summary>
        /// Disposes managed and unmanaged resources.
        /// </summary>
        /// <param name="isDisposing">Flag indicating how this protected method was called. 
        /// TRUE means via Dispose(), FALSE means via the destructor.
        /// Only in case of a call through the Dispose() method should managed resources be freed.</param>
        private void Dispose(bool isDisposing)
        {
			if (!this.isDisposed)
			{
				// Dispose the workspace services.
				if (null != this.authenticationService)
				{
					this.authenticationService.Dispose();
				}

				if (null != this.authorizationService)
				{
					this.authorizationService.Dispose();
				}

				if (null != this.sequencerService)
				{
					this.sequencerService.Dispose();
				}
			}

			this.isDisposed = true;
        }

        /// <summary>
        /// Create a new session.
        /// </summary>
        /// <param name="credentials"></param>
        private Guid CreateSession(Credential credentials)
        {
            string userID = string.Empty;

            if (this.authenticationService.TryAuthenticate(credentials, out userID))
            {
                this.SessionID = this.sessionService.CreateSession(userID);
            }
            else
            {
                throw new SecurityException(ExceptionNumber.InvalidCredentials);
            }

            return this.SessionID;
        }

        /// <summary>
        /// Retrieves a service of the specified type.
        /// </summary>
        /// <typeparam name="T">The service type.</typeparam>
        /// <returns>The service instance.</returns>
        /// <remarks>An instance of a service can only be retrieved via <see cref="ApplicationModel.Portal"/>.</remarks>
        private T GetService<T>()
            where T : IApplicationService, new()
        {
            SessionService.Session session;

            if (this.sessionService.TryGetSession(this.SessionID, out session))
            {
                return session.GetService<T>();
            }
            else
            {
                throw new SessionException(ExceptionNumber.SessionExpired);
            }
        }

		/// <summary>
		/// Stores a data object to the default storage level.
		/// </summary>
		/// <typeparam name="T">The type of the data object.</typeparam>
		/// <param name="key">The key for the data object.</param>
		/// <param name="data">The data object.</param>
		private void StoreUserData<T>(string key, T data)
		{
			SessionService.Session session = null;
			if (this.sessionService.TryGetSession(this.SessionID, out session))
			{
				this.userProfileService.Store<T>(session.SessionRegistryID, key, data);
			}
		}

		/// <summary>
		/// Stores a data object to the given storage level.
		/// </summary>
		/// <typeparam name="T">The type of the data object.</typeparam>
		/// <param name="key">The key for the data object.</param>
		/// <param name="data">The data object.</param>
		/// <param name="storageLevel">The storage level.</param>
		private void StoreUserData<T>(string key, T data, UserProfileStorageLevelType storageLevel)
		{
			SessionService.Session session = null;
			if (this.sessionService.TryGetSession(this.SessionID, out session))
			{
				this.userProfileService.Store<T>(session.SessionRegistryID, storageLevel, key, data);
			}
		}

		/// <summary>
		/// Retrieves the data object with the given key from the default storage level.
		/// </summary>
		/// <typeparam name="T">The type of the data object.</typeparam>
		/// <param name="key">The key for the data object.</param>
		/// <returns>The data object of type T.</returns>
		private T RetrieveUserData<T>(string key)
		{
			T result = default(T);

			SessionService.Session session = null;
			if (this.sessionService.TryGetSession(this.SessionID, out session))
			{
				result = this.userProfileService.Retrieve<T>(session.SessionRegistryID, key);
			}

			return result;
		}

		/// <summary>
		/// Retrieves the data object with the given key from the given storage level.
		/// </summary>
		/// <typeparam name="T">The type of the data object.</typeparam>
		/// <param name="key">The key for the data object.</param>
		/// <param name="storageLevel">The storage level.</param>
		/// <returns>The data object of type T.</returns>
		private T RetrieveUserData<T>(string key, UserProfileStorageLevelType storageLevel)
		{
			T result = default(T);

			SessionService.Session session = null;
			if (this.sessionService.TryGetSession(this.SessionID, out session))
			{
				result = this.userProfileService.Retrieve<T>(session.SessionRegistryID, storageLevel, key);
			}

			return result;
		}

        /// <summary>
        /// Destroys a current session.
        /// </summary>
        private void DestroySession()
        {
            this.DestroySession(this.SessionID);
        }

        /// <summary>
        /// Destroys the session with the given session identifier.
        /// </summary>
        /// <param name="sessionID">The session identifier.</param>
        private void DestroySession(Guid sessionID)
        {
            this.sessionService.DestroySession(sessionID);
        }

        #endregion
    }

    #endregion
}
